#include "mainWindow.h"
#include <cmath>
#include <QMessageBox>
#include <QMenu>
#include <QFileDialog>
#include <QWindow>
#include "log.h"
#include "symTable.h"
#include "ChartView.h"
#include "SignalItem.h"
#include "compiler.h"
#include "calculator.h"

using namespace QtCharts;

MainWindow::MainWindow(QWidget* parent): QMainWindow(parent), sigSuffix(0)
{
    this->logWindow = new LoggerWindow(this);

    UI_INFO("Start init");

    ui.setupUi(this);
    this->setWindowIcon(QIcon(":/icon/mainIcon"));

    ui.pSignalEditorWidget->setUp();

    this->pSeries = new QLineSeries;
    this->pAxisX = new QValueAxis();
    this->pAxisY = new QValueAxis();

    auto pChart = new QChart();
    pChart->setAnimationOptions(QChart::NoAnimation);
    pChart->setBackgroundVisible();
    pChart->autoFillBackground();
    pChart->addAxis(this->pAxisX, Qt::AlignBottom);
    pChart->addAxis(this->pAxisY, Qt::AlignLeft);
    pChart->legend()->setVisible(false);
    pChart->addSeries(this->pSeries);
    this->pSeries->attachAxis(this->pAxisX);
    this->pSeries->attachAxis(this->pAxisY);

    ui.pSignalChart->setChart(pChart);

    ui.pSignalList->setDragDropMode(SignalListWidget::InternalMove);

    this->pMenu = new QMenu(ui.pSignalList);
    this->pMenu->addActions({ ui.actNewSig, ui.actDelSig ,ui.actImport, ui.actExport });

    Calculator_t::getInst().setTotolPoint(ui.pCalNum->text().toInt());

    ui.pCalNum->setValidator(new QRegExpValidator(QRegExp("\\d+")));

    connect(ui.actNewSig, &QAction::triggered, this, &MainWindow::addSignal);
    connect(ui.actDelSig, &QAction::triggered, this, &MainWindow::delSignal);
    connect(ui.actImport, &QAction::triggered, this, &MainWindow::importWorkspace);
    connect(ui.actExport, &QAction::triggered, this, &MainWindow::exportWorkspace);

    connect(ui.pbAddSig, &QPushButton::clicked, this, &MainWindow::addSignal);
    connect(ui.pbDelSig, &QPushButton::clicked, this, &MainWindow::delSignal);

    connect(ui.pSignalList, &QListWidget::currentItemChanged, this, &MainWindow::enableExpress);

    connect(ui.pCalculateButton, &QPushButton::clicked, this, &MainWindow::calculateCurSig);
    connect(ui.pSignalList, &QListWidget::itemChanged, this, &MainWindow::itemChanged);
    connect(ui.pSignalList, &QListWidget::currentItemChanged, this, &MainWindow::currentItemChanged);
    connect(ui.pSignalList, &QListWidget::customContextMenuRequested, this, [this](const QPoint& pos) {this->pMenu->exec(ui.pSignalList->mapToGlobal(pos));});
    connect(ui.pbLog, &QPushButton::clicked, this->logWindow, &LoggerWindow::show);

#ifdef ANDROID
    Compiler_t::loadExtLibs("../lib");
#else
    Compiler_t::loadExtLibs("./lib");
#endif
    UI_INFO("Init done");
}

MainWindow::~MainWindow()
{
    UI_INFO("Destroy");
}

void MainWindow::importWorkspace(void)
{
    //这个只能选择已存在的文件
    QString fileName = QFileDialog::getOpenFileName(this, tr("select file to load"), "workspace_demo", "*.json");

    if (fileName.isEmpty())
    {
        QMessageBox::critical(this, tr("file error"), tr("not select a file."));
        return;
    }

    QFile file(fileName);
    if (not file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QMessageBox::critical(this, tr("file error"), tr("can`t open: %1").arg(fileName));
        file.close();
        return;
    }

    const QJsonDocument& doc = QJsonDocument::fromJson(file.readAll());
    if (not doc.isObject())//json无效
    {
        QMessageBox::critical(this, tr("file error"), tr("file format error"));
        file.close();
        return;
    }

    const QJsonObject& root = doc.object();

    auto sigArr = root.find(this->arrayKey);
    auto fs = root.find(this->fsKey);
    auto fsUnit = root.find(this->fsUnitKey);
    auto calPoints = root.find(this->calPointsKey);
    auto end = root.end();

    if (sigArr == end || fs == end || fsUnit == end || calPoints == end || \
    false == sigArr.value().isArray() || false == fs.value().isDouble() || \
    false == fsUnit.value().isDouble() || false == calPoints.value().isDouble())
    {
        QMessageBox::critical(this, tr("file error"), tr("file format error"));
        file.close();
        return;
    }

    ui.pFs->setValue(fs.value().toDouble());
    ui.pFsScale->setCurrentIndex(fsUnit.value().toInt());
    ui.pCalNum->setText(QString::number(calPoints.value().toInt()));
    ui.pSignalList->load(sigArr.value().toArray());

    UI_INFO("load file:%s success", fileName.toStdString().c_str());

    return;
}

void MainWindow::exportWorkspace(void)
{
    //这个可以选择不存在的文件
    QString fileName = QFileDialog::getSaveFileName(this, tr("select file to save"), "workspace_demo", "*.json");

    if (fileName.isEmpty())
    {
        QMessageBox::critical(this, tr("file error"), tr("not select a file."));
        return;
    }

    QFile file(fileName);
    if (not file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
    {
        QMessageBox::critical(this, tr("file open fail"), tr("can`t open: %1").arg(fileName));
        file.close();
        return;
    }

    QJsonObject root;

    root[this->fsKey] = ui.pFs->text().toDouble();
    root[this->fsUnitKey] = ui.pFsScale->currentIndex();
    root[this->calPointsKey] = ui.pCalNum->text().toInt();
    root[this->arrayKey] = ui.pSignalList->save();

    QJsonDocument doc;

    doc.setObject(root);
    file.write(doc.toJson());
    file.close();

    UI_INFO("save to file:%s success", fileName.toStdString().c_str());
}

void MainWindow::addSignal(void)
{
    for (int i = 0;i < 32;i++)
    {
        QString itemName = this->sigName.arg(this->sigSuffix++);
        if (not SigSymTable.has(itemName))
        {
            auto newItem = new SignalItem(itemName);

            this->curItemText = itemName;
            ui.pSignalList->addItem(newItem);
            ui.pSignalList->editItem(newItem);
            SigSymTable.insert(itemName, newItem);

            UI_INFO("Add signal: %s", itemName.toStdString().c_str());
            return;
        }
    }
    QMessageBox::critical(this, tr("Name duplicate"), tr("Auto name could only try 32 times, if your workspace have a lot of auto-named signal, this problem will happen, but you can try add signal again to fix it."));
}

void MainWindow::delSignal(void)
{
    auto itemToDel = ui.pSignalList->takeItem(ui.pSignalList->currentRow());
    if (ui.pSignalList->currentRow() == -1)//若删除后当前选择项非法,则重新进入禁止编辑表达式的状态
    {
        connect(ui.pSignalList, &QListWidget::currentItemChanged, this, &MainWindow::enableExpress);
        ui.pSignalExpress->setDisabled(true);
    }

    if (itemToDel)
    {
        SigSymTable.remove(itemToDel->text());
        delete itemToDel;
    }
}

void MainWindow::enableExpress(void)
{
    ui.pSignalExpress->setEnabled(true);
    disconnect(ui.pSignalList, &QListWidget::currentItemChanged, this, &MainWindow::enableExpress);
}

void MainWindow::currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous)
{
    //隐藏光标
    ui.pSignalChart->hideCursor();

    //保存上一个
    if (nullptr != previous)
    {
        auto pItem = static_cast<SignalItem*>(previous);
        pItem->setSourceCode(ui.pSignalExpress->toPlainText());
        pItem->setFFTMode(ui.cbIsFFTmode->checkState() == Qt::Checked);
    }

    //载入下一个
    if (nullptr != current)
    {
        auto pItem = static_cast<SignalItem*>(current);
        const QString& expr = pItem->getSourceCode();
        ui.pSignalExpress->setPlainText(expr);
        ui.cbIsFFTmode->setCheckState(pItem->getFFTMode() ? Qt::Checked : Qt::Unchecked);

        if (not expr.isEmpty())
            this->calculateCurSig();
        else
            this->pSeries->clear();
        this->curItemText = current->text();
    }
    else
    {
        UI_INFO("Invalid selection clear curItemText");
        this->curItemText.clear();
        ui.pSignalExpress->clear();
        this->pSeries->clear();
    }
}

void MainWindow::itemChanged(QListWidgetItem* item)
{
    if (not this->curItemText.isEmpty())
    {
        const QString& newName = item->text();
        if (this->sigNameRule.exactMatch(newName))//新的信号名称符合要求
        {
            UI_INFO("Item changed from: %s to: %s", this->curItemText.toStdString().c_str(), newName.toStdString().c_str());
            SigSymTable.remove(this->curItemText);
            this->curItemText = newName;
            if (SigSymTable.insert(this->curItemText, static_cast<SignalItem*>(item)))
            {
                return;
            }
            else
            {
                QMessageBox::critical(this, tr("Name duplicate"), tr("This name is same as another signal name which already exists."));
            }
        }
        else
        {
            QMessageBox::critical(this, tr("Name illegal"), tr("This name not match naming rules."));
        }

        ui.pSignalList->blockSignals(true);
        item->setText(this->curItemText);
        ui.pSignalList->blockSignals(false);
    }
}

void MainWindow::on_pCalNum_editingFinished(void)
{
    //这里要屏蔽掉输入框的信号,因为下面的MessageBox会获取输入焦点,所以会再次触发editingFinished
    ui.pCalNum->blockSignals(true);

    int newValue = ui.pCalNum->text().toInt();
    auto& calculator = Calculator_t::getInst();

    if (newValue >= this->calPointMax || newValue < this->calPointMin)
    {
        QMessageBox::critical(this, tr("Calculate point error"),
        tr("%1 is a illegal value (valid range[%2 - %3])").arg(newValue).arg(this->calPointMin).arg(this->calPointMax)
        );
        newValue = calculator.getTotolPoint();
    }
    else
    {
        calculator.setTotolPoint(newValue);
    }

    //这里究竟是保持当前编辑值还是恢复上一次的正确值,两种策略好像都合理,暂定保持编辑值
    // ui.pCalNum->setText(QString("%1").arg(newValue));
    ui.pCalNum->blockSignals(false);
}

void MainWindow::calculateCurSig(void)
{
    auto curCode = ui.pSignalExpress->toPlainText();

    if (curCode.isEmpty())
        return;

    auto pItem = static_cast<SignalItem*>(ui.pSignalList->currentItem());
    pItem->setSourceCode(curCode);//将编辑框代码保存至item
    bool isFFTMode = ui.cbIsFFTmode->checkState() == Qt::Checked;
    pItem->setFFTMode(isFFTMode);

    //获取编译器和计算器的实例
    auto& calculator = Calculator_t::getInst();
    auto& compiler = Compiler_t::getInst();

    //设置计算器使用的采样率
    float fs = ui.pFs->text().toFloat() * this->fsScale[ui.pFsScale->currentIndex()];
    calculator.setFS(fs);

    int calNum = ui.pCalNum->text().toInt();
    calculator.setTotolPoint(calNum);

    float maxValue = 0, minValue = 0;

    if (true == compiler.compile(pItem))//计算之前先编译当前item,item内部会根据item的源码是否修改选择性编译
    {
        float* res = new float[calNum];
        calculator.calculate(pItem, res);

        pSeries->clear();

        if (false == isFFTMode)
        {
            for (int calPoint = 0;calPoint < calNum;calPoint++)
            {
                float& curValue = res[calPoint];
                maxValue = __max(curValue, maxValue);
                minValue = __min(curValue, minValue);
                this->pSeries->append(calPoint, curValue);
            }
        }
        else
        {
            auto factor = fs / calNum;
            for (int calPoint = 0;calPoint < calNum / 2;calPoint++)
            {
                float& curValue = res[calPoint];
                maxValue = __max(curValue, maxValue);
                minValue = __min(curValue, minValue);
                this->pSeries->append(calPoint * factor, curValue);
            }
        }

        delete[] res;

        this->pSeries->setName(ui.pSignalList->currentItem()->text());

        if (true == isFFTMode)//频谱模式,横轴需要直接解算为对应的频率值
        {
            this->pAxisX->setRange(0, fs / 2);
        }
        else//非频谱模式, 范围直接就是值的范围
        {
            this->pAxisX->setRange(0, calNum);
        }

        this->pAxisY->setRange(minValue * 1.15 - 1, maxValue * 1.15 + 1);
    }
    else
    {
        UI_ERROR("Signal compile fail");
    }
}
